package ecs.net.systems;

import ecs.core.BaseSystem;
import ecs.net.ByteBuffer;
import ecs.net.EventType;
import ecs.net.Status;
import ecs.net.components.PackBufferComponent;
import ecs.net.components.ReceiveDataComponent;
import ecs.net.components.SocketComponent;
import ecs.net.components.StatusComponent;

import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.util.concurrent.CompletableFuture;

public class ReceiveDataSystem extends BaseSystem {

    public ReceiveDataSystem() {
        super();
        interestingComponentTypes(new Class[]{SocketComponent.class, PackBufferComponent.class
                , StatusComponent.class, ReceiveDataComponent.class});
    }

    public void startReceive() {
        CompletableFuture.runAsync(() -> {
            entityComponents().forEach(components -> {
                StatusComponent status = (StatusComponent) components.get(2);
                if (status.status() == Status.Connected) {
                    SocketComponent socket = (SocketComponent) components.get(0);
                    PackBufferComponent packBuffer = (PackBufferComponent) components.get(1);
                    ReceiveDataComponent receiveData = (ReceiveDataComponent) components.get(4);
                    receive(socket, packBuffer, status, receiveData);
                }
            });
            startReceive();
        });
    }

    private void receive(SocketComponent socket, PackBufferComponent packBuffer, StatusComponent status, ReceiveDataComponent receiveData) {
        InputStream in = null;
        try {
            in = socket.socket().getInputStream();
            int readCount = unpack(packBuffer, in, receiveData);
            if (readCount < 0) {
                in.close();
                status.status(Status.Disconnected);
                broadcast(EventType.SocketStatusChanged);
            }
        } catch (SocketTimeoutException e) {
            status.status(Status.Timeout);
            broadcast(EventType.SocketStatusChanged, e);
        } catch (IOException e) {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ignored) {
                }
            }
            status.status(Status.Closed);
            broadcast(EventType.SocketStatusChanged, e);
        }
    }

    private int unpack(PackBufferComponent packBuffer, InputStream in, ReceiveDataComponent receiveData) throws IOException {
        int readCount = packBuffer.buffer().write(in);
        while (packBuffer.buffer().readRemaining() > 0) {
            if (packBuffer.last() == null) {
                ByteBuffer.copy(packBuffer.lenBuffer(), packBuffer.buffer());
                if (packBuffer.lenBuffer().readRemaining() == 4) {
                    int len = packBuffer.lenBuffer().readInt();
                    packBuffer.last(new ByteBuffer(len));
//                    System.out.println("create "+len);
                } else {
                    throw new RuntimeException("lenBuffer.readRemaining() != 4");
                }
            }
            ByteBuffer.copy(packBuffer.buffer(), packBuffer.last());
//            System.out.println("last "+ last + " reaming "+ last.remaining());
            if (packBuffer.last().writeRemaining() == 0) {
//                System.out.println(" pack  complete");
                receiveData.data().enqueue(packBuffer.last());
                packBuffer.last(null);
                packBuffer.lenBuffer().clear();
            } else if (packBuffer.last().writeRemaining() < 0) {
                throw new RuntimeException("last.writeRemaining() < 0 isn't enable!");
            }
        }
        packBuffer.buffer().clear();
        return readCount;
    }
}
